<?php
/**
 * @author iuga
 */

require_once('XMLParser.class.php');
require_once('TiposDeDatos/TipoDatoMySQL.class.php');
require_once('BaseDeDatos.class.php');

class Mapper
{
    private $nombreTabla;
    private $nombreClase;
    private $ids;
    private $atributos;

    /**
     * Construye y parsea el XML tambien carga por primera vez los array de
     * ids y de atributos para tenerlos a mano.
     * @param <String> $xml
     */
    public function Mapper($xml)
    {
        $this->ids         = Array();
        $this->atributos   = Array();
        
        // Parseo el XML
        $xmlPar = new XMLParser($xml);

        $this->ids         = $xmlPar->getIds();
        $this->atributos   = $xmlPar->getAtributos();
        $this->nombreClase = $xmlPar->getNombreClase();
        $this->nombreTabla = $xmlPar->getNombreTabla();
    }
   
    /**
    * Brinda el método cargar el cual se encarga de retornar el objeto
    * del tipo indicado en el XML cargado con todos sus datos desde la BD
    * vía el XML.
    * @param <Array> $ids
    * @return <Object> $objetoCreado
     *@todo [OK]
    */
    public function Cargar($ids)
    {
        $sql = 'SELECT ';
        // Lleno con los ids
        $it = new ArrayIterator($this->ids);
        while($it->valid())
        {
            $fila = $it->current();
            $sql .= $fila['table'];
            $it->next();
            if($it->valid())
            {
                $sql.=', ';
            }
        }
        // Cambio a atributos
        $sql.=', ';
        // Lleno los atributos
        $it = new ArrayIterator($this->atributos);
        while($it->valid())
        {
            $fila = $it->current();
            $sql .= $fila['table'];
            $it->next();
            if($it->valid())
            {
                $sql.=', ';
            }
        }
        // Cargo la Tabla
        $sql .= ' FROM ';
        $sql .= $this->nombreTabla;
        // Creo el WHERE
        $sql .= ' WHERE ';

        if(is_array($ids))
        {
           if((count($ids)!=count($this->ids)))
           {
               throw new Exception('La cantidad de PK no coincide con las definidas en el XML');
           }
           $it = new ArrayIterator($this->ids);
           $i = 0;
           while($it->valid())
            {
                $fila = $it->current();
                $sql .= $fila['table'];
                $sql .= '=';
                $sql .= $ids[$i];

                $it->next();
                if($it->valid())
                {
                    $sql.=' AND ';
                    $i++;
                }
            }         
        }       
        if(!is_array($ids))
        {
           $it = new ArrayIterator($this->ids);
           $i = 0;
           while($it->valid())
            {
                $fila = $it->current();
                $sql .= $fila['table'];
                $sql .= '=';
                $sql .= $ids;
                $it->next();
            }          
        }

       //echo 'SQL: '.$sql;
        // Ejecutamos la consulta
        // Armamos la conexion a la Base De Datos
       $bd = BaseDeDatos::getInstance();
       // Ahora, ejecutamos la consulta
       if($res = $bd->Query($sql))
       {  
           if($bd->getNumObjetos($res)==0)
           {
               throw new Exception("Objeto inexistente");
           }
           // Se ejecuto bien, continuamos
           $datos = $bd->leerObjeto($res);
           // Creo el nuevo objeto destino
           $rc  =  new ReflectionClass($this->nombreClase);
           $obj =  $rc->newInstance();
           //Cargo el valor de las PK
           foreach($this->ids AS $ids)
           {
               // Armo el setter
               $set = 'set';
               $set.= $ids['name'];
               $tabla = strval($ids['table']);
               $obj->$set($datos->$tabla);   
           }            
           //Cargo el valor de los atributos
           foreach($this->atributos AS $atr)
           {
               // Armo el setter
               $set = 'set';
               $set.= $atr['name'];
               $tabla = strval($atr['table']);
               $obj->$set($datos->$tabla);   
           }
           return $obj;
       }
       return null;
    }

    /**
    * ObtenerTodos trae un Array con todas las instancias de los objetos
    * encontrados en la BD
    * @return <Array> $listaObjetos
    * @todo [OK]
    */
    public function ObtenerTodos($orden = null)
    {
        // Array a devolver
        $todos = Array();
        $sql = 'SELECT ';
        // Solo busco los ids
        $it = new ArrayIterator($this->ids);
        while($it->valid())
        {
            $fila = $it->current();
            $sql .= $fila['table'];
            $it->next();
            if($it->valid())
            {
                $sql.=', ';
            }
        }     
        // FROM
        $sql .= ' FROM ';
        $sql .= $this->nombreTabla;
        // Si me pasan un ordenamiento, lo ordeno =)
        if($orden != null)
        {
            $sql .= ' ORDER BY '.strval($orden);
        }
        // Ahora cargo todos los datos con Cargar();
       //echo 'SQL:'.$sql;

       $bd = BaseDeDatos::getInstance();
       // Ahora, ejecutamos la consulta
       if($res = $bd->Query($sql))
       {        
           // Se ejecuto bien, continuamos
           while($datos = $bd->leerObjeto($res))
           {
               $listaId = Array();
               foreach ($this->ids as $i)
               {
                 $listaId[] = $datos->$i['table'];
               }
               $todos[] = $this->Cargar($listaId);
           }
           return $todos;
       }
       else
       {
           return null;
       }

    }

   /**
    * Filtrar trae un Array con todas las instancias de los objetos bajo la condicion
    * @return <String> $sqlWhere 
    * @todo [OK]
    */
    public function Filtrar($sqlWhere,$orden = null)
    {
        $sql = 'SELECT ';
        // Solo busco los ids
        $it = new ArrayIterator($this->ids);
        while($it->valid())
        {
            $fila = $it->current();
            $sql .= $fila['table'];
            $it->next();
            if($it->valid())
            {
                $sql.=', ';
            }
        }     
        // FROM
        $sql .= ' FROM ';
        $sql .= $this->nombreTabla;
        // Agrego el filtrado
        if($sqlWhere != null and is_string($sqlWhere))
        {
            $sql .= ' WHERE '.$sqlWhere;
        }
        // Si me pasan un ordenamiento, lo ordeno =)
        if($orden != null)
        {
            $sql .= ' ORDER BY '.strval($orden);
        }
        // Ahora cargo todos los datos con Cargar();

       $bd = BaseDeDatos::getInstance();
       // Ahora, ejecutamos la consulta
       if($res = $bd->Query($sql))
       {        
           // Se ejecuto bien, continuamos
           while($datos = $bd->leerObjeto($res))
           {
               $listaId = Array();
               foreach ($this->ids as $i)
               {
                 $listaId[] = $datos->$i['table'];
               }
               $todos[] = $this->Cargar($listaId);
           }
           return $todos;
       }
       else
       {
           return null;
       }
    }    
    
   /**
    * Como lo define la interface, si el objeto tiene ID, actualiza sus datos
    * Sino tiene id, crea el objeto en la BD
    * @param <Objeto> $obj
    * @return <Boolean> $exito 
    * @todo [OK]
    */
    public function Guardar($obj)
    {
        // Si no es un objeto del tipo mapeado retorno false
        if(!$obj instanceof $this->nombreClase)
        {
            return false;
        }
               // Lo primero que hago es ver si el objeto tiene cargados el id
               $flagok = true; // estan cargados todos los id, si no, se vuelve falso
               foreach ($this->ids as $id) 
               {

                   $get = 'get'.$id['name'];
                   $valor = $obj->$get();
                   if(!isset($valor))
                   {
                       $flagok = false;
                   }
               }
            
               // si tiene los ids, actualizo, sino creo
               if($flagok == true)
               {
                   return $this->Actualizar($obj);
               }
               else
               {
                   return $this->Crear($obj);
               }
    }
    
    /**
    * Crea el Obbjeto en la BD
    * @param <Objeto> $obj 
    * @return <Boolean> $exito  
     * @todo [OK]
    */
   private function Crear($obj)
   {

       // Vamos con la creacion, ARMAMOS LA ESTRUCTURA INSERT
           $sql = "INSERT INTO ".$this->nombreTabla;
           $sql .= ' ( ';
           
           // ITERO los IDS
            $it = new ArrayIterator($this->ids);
            while($it->valid())
            {
                $fila = $it->current();
                $sql .= $fila['table'];
                $it->next();
                if($it->valid())
                {
                    $sql.=', ';
                }
            }
            $sql .= ', ';
           // ITERO los atributos       
           $ita = new ArrayIterator($this->atributos);
           while($ita->valid())
           {
              $actual = $ita->current(); 
              $sql .= $actual['table'];
              $ita->next();
              if($ita->valid())
              {
                  $sql .= ',';
              }
           }
           
           $sql .= ' ) VALUES (';
           // Vamos con los datos
           $it->rewind();
           $ita->rewind();

           while($it->valid())
           {
               $actual = $it->current();
               $funcion = 'get'.$actual['name'];
               $val = $obj->$funcion();
               $tipoDato = $actual['sqlType'];

                // Creo el tipo de dato, lo valido y lo represento (AMO PATRONES)
                $dato =  TipoDatoMySQL::Crear($tipoDato);
                $dato->setValor($val);
           
                 // Ahora seteo las opciones especiales 
                  // Puede ser nulo ?
                  if(isset($actual['notnull'])) { $dato->esNotNull($actual['notnull']); }
                  // Es autoincrementable ?
                  if(isset($actual['autoincrement'])) { $dato->esAutoincrement($actual['autoincrement']); }    
           
                $sql .= $dato->get2SQL();

                $it->next();
                if($it->valid())
                {
                  $sql .= ',';
                }
           }
           $sql .= ',';
           while($ita->valid())
           {
               $actual = $ita->current();
               $funcion = 'get'.$actual['name'];
               $val = $obj->$funcion();
               $tipoDato = $actual['sqlType'];

                // Creo el tipo de dato, lo valido y lo represento (AMO PATRONES)
                $dato =  TipoDatoMySQL::Crear($tipoDato);
                $dato->setValor($val);

                 // Ahora seteo las opciones especiales 
                  // Puede ser nulo ?
                  if(isset($actual['notnull'])) { $dato->esNotNull($actual['notnull']); }
                  // Es autoincrementable ?
                  if(isset($actual['autoincrement'])) { $dato->esAutoincrement($actual['autoincrement']); }           
           
                $sql .= $dato->get2SQL();

                $ita->next();
                if($ita->valid())
                {
                  $sql .= ',';
                }
           }
           $sql .= ')';
           //echo $sql;
           $bd = BaseDeDatos::getInstance();
           // Ahora, ejecutamos la consulta
           if($res = $bd->Query($sql))
           {
              // Si se creo correctamente, cargo los ids en el objeto
              foreach ($this->ids as $id)
              {
                // Solo puede haber un autoincrement por tabla | Sino tiene no hago nada
                if(isset($id['autoincrement']) and $id['autoincrement']=='true')
                {
                  $set = 'set'.$id['name'];
                  $obj->$set($bd->IdInsertado());
                }                
              }
              return true;
           }
           return false;
            
   }
    
   
   /**
    * Actualiza los datos del objeto en la BD
    * @param <Objeto> $obj 
    * @return <Boolean> $exito 
    * @todo [OK]
    */
   private function Actualizar($obj)
   {
       $sql = 'UPDATE '.$this->nombreTabla. ' SET ';
           // Vamos con los nombre = valor;
           $it = new ArrayIterator($this->atributos);
           while($it->valid())
           {
               $actual = $it->current();
               $sql .= $actual['table'];
               $sql .= ' = ';
                    $tipoDato = strval($actual['sqlType']);
                    $nulo = strval($actual['notnull']);
                    $get = 'get'.$actual['name'];
                    $valorAtributo = $obj->$get();
  
                   // Creo el tipo de dato, lo valido y lo represento (AMO PATRONES)
                     $dato =  TipoDatoMySQL::Crear($tipoDato);
                     $dato->setValor($valorAtributo);

                         // Ahora seteo las opciones especiales
                          // Puede ser nulo ?
                          if(isset($actual['notnull'])) { $dato->esNotNull($actual['notnull']); }
                          // Es autoincrementable ?
                          if(isset($actual['autoincrement'])) { $dato->esAutoincrement($actual['autoincrement']); }

                      $sql .= $dato->get2SQL();        
           
               $it->next();
               if($it->valid())
               {
                   $sql .= ', ';
               }
           }
            $sql .= ' WHERE ';
     
            $it = new ArrayIterator($this->ids);
            while($it->valid())
            {
                $actual = $it->current();
                $sql .= $actual['table'];
                $sql .= ' = ';
                $get = 'get'.$actual['name'];
                $valorAtributo = $obj->$get();
                $tipoDato = $actual['sqlType'];

                   // Creo el tipo de dato, lo valido y lo represento (AMO PATRONES)
                     $dato =  TipoDatoMySQL::Crear($tipoDato);
                     $dato->setValor($valorAtributo);
                     $sql .= $dato->get2SQL();   

               $it->next();
               if($it->valid())
               {
                   $sql .= ', ';
               }               
            }        
       
            //echo $sql;
           $bd = BaseDeDatos::getInstance();
           // Ahora, ejecutamos la consulta
           if($res = $bd->Query($sql))
           {
               return true;
           }
           return false;
   }   
   
   
   /**
    * Da de baja o elimina el objeto, incluido los datos en la BD
    * @param <Objeto> $obj
    * @return <Boolean> $exito 
    * @todo [OK]
    */
   public function DarDeBaja($obj)
   {
       $sql = 'DELETE FROM '.$this->nombreTabla.' WHERE ';
            $it = new ArrayIterator($this->ids);
            while($it->valid())
            {
                $actual = $it->current();
                $sql .= $actual['table'];
                $tipoDato = $actual['sqlType'];
                $sql .= ' = ';
                $get = 'get'.$actual['name'];
                $valorAtributo = $obj->$get();

                   // Creo el tipo de dato, lo valido y lo represento (AMO PATRONES)
                     $dato =  TipoDatoMySQL::Crear($tipoDato);
                     $dato->setValor($valorAtributo);
                     $sql .= $dato->get2SQL();   

               $it->next();
               if($it->valid())
               {
                   $sql .= ', ';
               }               
            }
           //echo $sql;
           $bd = BaseDeDatos::getInstance();
           // Ahora, ejecutamos la consulta
           if($res = $bd->Query($sql))
           {
               return true;
           }
           return false;
   }   
}
?>
